using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.IO;
using System.Xml;

namespace Woz
{
    /// <summary>
    /// the screen class represents a woz screen
    /// </summary>
    public class Screen
    {
        /// <summary>
        /// private constructor that does nothing
        /// </summary>
        private Screen()
        {
            // set the ID
            this.mID = ++sAutoID;
        }

        /// <summary>
        /// default screen constructor
        /// </summary>
        /// <param name="pName">the name of the screen</param>
        internal Screen(string pName, bool pHidden)
        {
            // set the ID
            this.mID = ++sAutoID;

            // set the name of this screen
            this.mName = pName;

            // set the screen to not be hidden
            this.mHidden = pHidden;

            // initialize the list of entities
            this.mEntities = new List<Woz.Entity>();

            // initialize the list of selected entities
            this.mSelectedEntities = new List<Woz.Entity>();

            // initialize the list of next screens
            this.mNextScreens = new List<Woz.Screen>();

            // initialize the bounding box of the selected entities
            this.mSelectedEntitiesBoundingBox = Woz.Entity.ComputeBoundingBox(this.mSelectedEntities.ToArray());

            // initialize the thumbnail image
            this.mThumbnailImage = new Bitmap(Woz.StaticVariables.Instance.ThumbnailImageSize.Width, Woz.StaticVariables.Instance.ThumbnailImageSize.Height, PixelFormat.Format32bppRgb);

            // create the screen thumbnail for the thumbnail slider
            this.mSliderThumbnail = new Woz.ScreenThumbnail(this);

            // create the screen thumbnail for the edit transitions mode
            this.mTransitionsThumbnail = new Woz.ScreenThumbnail(this);
            //this.mTransitionsThumbnail.Readonly = true;

            // create the screen thumbnail for the propagate last change dialog
            this.mPropagateThumbnail = new Woz.ScreenThumbnail(this);
            this.mPropagateThumbnail.Readonly = true;

            // clear the screen
            this.Clear();
        }

        /// <summary>
        /// gets the unique ID of this screen
        /// </summary>
        public uint ID
        {
            get
            {
                return this.mID;
            }
            //internal set
            //{
            //    // set the ID
            //    this.mID = value;

            //    // update the auto id
            //    sAutoID = Math.Max(sAutoID, value + 1);
            //}
        }

        /// <summary>
        /// gets the name of this screen
        /// </summary>
        public string Name
        {
            get
            {
                return this.mName;
            }
            set
            {
                // make sure this is not the same name
                if (this.mName == value)
                {
                    return;
                }

                // get the next available screen
                string newName = Woz.ScreenManager.Instance.GetNextAvailableName(value);

                // set the screen name to the new name
                this.mName = newName;

                // update all the screen thumbnails
                this.mSliderThumbnail.Invalidate();
                this.mTransitionsThumbnail.Invalidate();
                this.mPropagateThumbnail.Invalidate();
            }
        }

        /// <summary>
        /// gets the thumbnail image associated with this screen
        /// </summary>
        public Image ThumbnailImage
        {
            get
            {
                return this.mThumbnailImage;
            }
        }

        /// <summary>
        /// gets the thumbnail control associated with the screen slider
        /// </summary>
        public Woz.ScreenThumbnail SliderThumbnail
        {
            get
            {
                return this.mSliderThumbnail;
            }
        }

        /// <summary>
        /// gets the thumbnail control associated with the edit screen transitions mode
        /// </summary>
        public Woz.ScreenThumbnail TransitionsThumbnail
        {
            get
            {
                return this.mTransitionsThumbnail;
            }
        }

        /// <summary>
        /// gets the thumbnail control associated with the propagate last change dialog
        /// </summary>
        public Woz.ScreenThumbnail PropagateThumbnail
        {
            get
            {
                return this.mPropagateThumbnail;
            }
        }

        /// <summary>
        /// gets or whether the screen is hidden or not
        /// </summary>
        public bool Hidden
        {
            get
            {
                return this.mHidden;
            }
            set
            {
                this.mHidden = true;
            }
        }

        /// <summary>
        /// gets an array of the all the entities on the screen
        /// </summary>
        public Woz.Entity[] Entities
        {
            get
            {
                return this.mEntities.ToArray();
            }
        }

        /// <summary>
        /// gets an array of the selected entities
        /// </summary>
        public Woz.Entity[] SelectedEntities
        {
            get
            {
                return this.mSelectedEntities.ToArray();
            }
        }

        /// <summary>
        /// gets the next screens
        /// </summary>
        public Woz.Screen[] NextScreens
        {
            get
            {
                return this.mNextScreens.ToArray();
            }
        }

        /// <summary>
        /// gets the bounding box of the selected entities
        /// </summary>
        public Rectangle SelectedEntitiesBoundingBox
        {
            get
            {
                return this.mSelectedEntitiesBoundingBox;
            }
        }

        /// <summary>
        /// gets whether the given entity is contained in this screen
        /// </summary>
        /// <param name="pEntity">the entity to search for</param>
        /// <returns>true if the entity is inside this screen</returns>
        public bool Contains(Woz.Entity pEntity)
        {
            return this.mEntities.Contains(pEntity);
        }

        /// <summary>
        /// add a screen to the next screens
        /// </summary>
        /// <param name="pScreen">the new screen to add to the next screens</param>
        public void AddNextScreen(Woz.Screen pScreen)
        {
            if (!this.mNextScreens.Contains(pScreen))
            {
                if (!pScreen.Hidden)
                {
                    this.mNextScreens.Add(pScreen);
                }
            }
        }

        /// <summary>
        /// removes a screen from the next screens
        /// </summary>
        /// <param name="pScreen">the new screen to add to the next screens</param>
        public void RemoveNextScreen(Woz.Screen pScreen)
        {
            this.mNextScreens.Remove(pScreen);
        }

        /// <summary>
        /// clone this screen and return a reference to the clone
        /// </summary>
        /// <param name="pName">the name to give the clone</param>
        public Woz.Screen Clone(string pName)
        {
            // create the new screen using the empty private constructor
            Woz.Screen screen = new Woz.Screen();

            // set the name of this screen
            screen.mName = pName;

            // set whether the screen is hidden
            screen.mHidden = this.mHidden;

            // create the entities
            screen.mEntities = new List<Woz.Entity>();

            // clone the pscreen entities to this screen
            foreach (Woz.Entity entity in this.mEntities)
            {
                // increment the reference count
                entity.RefCount++;

                // add it to the screen
                screen.mEntities.Add(entity);
            }

            // create the selected entities
            screen.mSelectedEntities = new List<Woz.Entity>();

            // create the list of next screens
            screen.mNextScreens = new List<Woz.Screen>();

            // clone the next screens
            foreach (Woz.Screen nextScreen in this.mNextScreens)
            {
                screen.mNextScreens.Add(nextScreen);
            }

            // clone the thumbnail image
            Rectangle thumbRect = new Rectangle(0, 0, this.mThumbnailImage.Width, this.mThumbnailImage.Height);
            screen.mThumbnailImage = this.mThumbnailImage.Clone(thumbRect, PixelFormat.Format32bppRgb) as Bitmap;

            // clone the screen thumbnails
            screen.mSliderThumbnail = new Woz.ScreenThumbnail(screen);

            // clone the transition thumbnail
            screen.mTransitionsThumbnail = new Woz.ScreenThumbnail(screen);
            //screen.mTransitionsThumbnail.Readonly = true;

            // clone the propagate last change dialog thumbnail
            screen.mPropagateThumbnail = new Woz.ScreenThumbnail(screen);
            screen.mPropagateThumbnail.Readonly = true;

            // return it
            return screen;
        }

        /// <summary>
        /// add an entity to the screen
        /// </summary>
        /// <param name="pEntity">the entity to add</param>
        /// <param name="pUpdateThumbnail">whether to update the thumbnail or not</param>
        public void AddEntity(Woz.Entity pEntity, bool pUpdateThumbnail)
        {
            // add the entity
            this.mEntities.Add(pEntity);
            
            // increment the reference count
            pEntity.RefCount++;

            // update the screen 
            if (pUpdateThumbnail)
            {
                this.UpdateThumbnail(UpdateMode.Add, pEntity);
            }
        }

        /// <summary>
        /// insert an entity to the screen
        /// </summary>
        /// <param name="pEntity">the entity to insert</param>
        /// <param name="pIndex">the index at which to insert it</param>
        /// <param name="pUpdateThumbnail">whether to update the thumbnail or not</param>
        public void InsertEntity(Woz.Entity pEntity, int pIndex, bool pUpdateThumbnail)
        {
            // add the entity
            this.mEntities.Insert(pIndex, pEntity);

            // increment the reference count
            pEntity.RefCount++;

            // update the screen 
            if (pUpdateThumbnail)
            {
                this.UpdateThumbnail(UpdateMode.Add, pEntity);
            }
        }

        /// <summary>
        /// remove an entity from the screen
        /// </summary>
        /// <param name="pEntity">the entity to remove</param>
        /// <param name="pUpdateThumbnail">whether to update the thumbnail</param>
        public void RemoveEntity(Woz.Entity pEntity, bool pUpdateThumbnail)
        {
            // remove the entity
            if(this.mEntities.Remove(pEntity))
            {
                // decrement the reference count
                pEntity.RefCount--;
            }

            // update the screen
            if (pUpdateThumbnail)
            {
                this.UpdateThumbnail(UpdateMode.Remove, pEntity);
            }
        }

        /// <summary>
        /// add a selected entity to the screen
        /// </summary>
        /// <param name="pEntity">the entity to add</param>
        /// <param name="pUpdateBoundingBox">whether to update the bounding box or not</param>
        public void SelectEntity(Woz.Entity pEntity, bool pUpdateBoundingBox)
        {
            // add the entity
            this.mSelectedEntities.Add(pEntity);

            // update the selected entities bounding box
            if (pUpdateBoundingBox)
            {
                this.UpdateSelectedEntitiesBoundingBox();
            }
        }

        /// <summary>
        /// deselect an entity from the screen
        /// </summary>
        /// <param name="pEntity">the entity to remove</param>
        /// <param name="pUpdateBoundingBox">whether to update the bounding box or not</param>
        public void DeselectEntity(Woz.Entity pEntity, bool pUpdateBoundingBox)
        {
            // remove the entity
            this.mSelectedEntities.Remove(pEntity);

            // update the selected entities bounding box
            if (pUpdateBoundingBox)
            {
                this.UpdateSelectedEntitiesBoundingBox();
            }
        }

        /// <summary>
        /// update the bouding box of the selected entity
        /// </summary>
        public void UpdateSelectedEntitiesBoundingBox()
        {
            // update the bounding box of the selected entities
            this.mSelectedEntitiesBoundingBox = Woz.Entity.ComputeBoundingBox(this.mSelectedEntities.ToArray());
        }

        /// <summary>
        /// draw the screen using the given graphics object
        /// </summary>
        /// <param name="pGraphics">the graphics object to draw with</param>
        public void Draw(Graphics pGraphics)
        {
            // draw each entity
            foreach (Woz.Entity entity in this.mEntities)
            {
                entity.Draw(pGraphics);
            }
        }

        /// <summary>
        /// saves the screen to the given stream
        /// </summary>
        /// <param name="pStream">the stream to save to</param>
        public void Save(XmlWriter pStream)
        {
            pStream.WriteStartElement("Screen", "urn:Woz");
            pStream.WriteAttributeString("ID", this.mID.ToString());
            pStream.WriteElementString("Name", this.mName);
            pStream.WriteElementString("Hidden", this.mHidden.ToString());
            pStream.WriteStartElement("Entities");
            foreach (Woz.Entity entitiy in this.mEntities)
            {
                pStream.WriteStartElement("Entity", "urn:Woz");
                pStream.WriteAttributeString("ID", entitiy.ID.ToString());
                pStream.WriteEndElement();
            }
            pStream.WriteEndElement();
            pStream.WriteEndElement();
        }

        /// <summary>
        /// performs a hittest on the screen and returns the first entity this point intersects
        /// </summary>
        /// <param name="pPoint">the point of interest</param>
        /// <returns>the first entity the point intersects or null if point doesnt hit anything</returns>
        public Woz.Entity HitTest(Point pPoint)
        {
            // find the entity that intersects with the point
            for (int i = this.mEntities.Count - 1; i >= 0; i--)
            {
                if (this.mEntities[i].HitTest(pPoint.X, pPoint.Y))
                {
                    return this.mEntities[i];
                }
            }

            // point doesnt hit anything
            return null;
        }

        /// <summary>
        /// performs a hittest on the screen and returns the list of entities that the rectangle contains
        /// </summary>
        /// <param name="pRectangle">the rectangle to perform the hittest on</param>
        /// <param name="pPercentArea">the percent of area to select</param>
        /// <returns>the list of entities that the rectangle contains</returns>
        public Woz.Entity[] HitTest(Rectangle pRectangle, float pPercentArea)
        {
            // initialize the array containing all the entities
            List<Woz.Entity> selectedEntities = new List<Woz.Entity>();

            // go through all the entities and see if their bounding box is contained within the rectangle
            for (int i = this.mEntities.Count-1; i >= 0; i--)
            {
                // set temp entity
                Woz.Entity entity = this.mEntities[i];

                // calculate the intersection rectangle
                Rectangle intersectRect = Rectangle.Intersect(pRectangle, entity.BoundingBox);

                // if the intersection rectangle exists
                if (intersectRect != Rectangle.Empty)
                {
                    // calculate the area of the entity
                    int entityArea = entity.BoundingBox.Width * entity.BoundingBox.Height;

                    // calculate the area of the intersection
                    int intersectArea = intersectRect.Width * intersectRect.Height;

                    // check if we got the percentage of the area
                    if ((intersectArea / (float)entityArea) >= pPercentArea)
                    {
                        selectedEntities.Add(entity);
                    }
                }
            }

            // return the entities
            return selectedEntities.ToArray();
        }

        /// <summary>
        /// gets the z depth of an entity
        /// </summary>
        /// <param name="pEntity">the entity whose z depth you want</param>
        /// <returns>the z depth of the entity or negative one if the entity is not in this screen</returns>
        public int GetZDepth(Woz.Entity pEntity)
        {
            return this.mEntities.IndexOf(pEntity);
        }

        /// <summary>
        /// brings the entity to the front and returns its original depth
        /// </summary>
        /// <param name="pEntity">the entity to bring to front</param>
        /// <exception cref="ArgumentException">if the entity is not found in the screen</exception>
        /// <returns>the original z depth</returns>
        public int BringToFront(Woz.Entity pEntity)
        {
            // first check if this entity is in the screen
            if (!this.mEntities.Contains(pEntity))
            {
                throw new ArgumentException("Entity not found in {0}.", this.mName);
            }

            // now get the entity z index
            int originalZ = this.mEntities.IndexOf(pEntity);

            // only bring to front if its not already in the front
            if (originalZ != this.mEntities.Count - 1)
            {
                // remove it from this position
                this.mEntities.RemoveAt(originalZ);

                // add it to the end of the list
                this.mEntities.Add(pEntity);
            }

            // return the original z
            return originalZ;
        }

        /// <summary>
        /// sends the entity to the back and returns its original depth
        /// </summary>
        /// <param name="pEntity">the entity to send to back</param>
        /// <exception cref="ArgumentException">if the entity is not found in the screen</exception>
        /// <returns>the original z depth</returns>
        public int SendToBack(Woz.Entity pEntity)
        {
            // first check if this entity is in the screen
            if (!this.mEntities.Contains(pEntity))
            {
                throw new ArgumentException("Entity not found in {0}.", this.mName);
            }

            // now get the entity z index
            int originalZ = this.mEntities.IndexOf(pEntity);

            // only send to back if its not already back
            if (originalZ != 0)
            {
                // remove it from this position
                this.mEntities.RemoveAt(originalZ);

                // add it to the end of the list
                this.mEntities.Insert(0, pEntity);
            }

            // return the original z
            return originalZ;
        }

        /// <summary>
        /// brings the entity forward and returns its original depth
        /// </summary>
        /// <param name="pEntity">the entity to bring forward</param>
        /// <exception cref="ArgumentException">if the entity is not found in the screen</exception>
        /// <returns>the original z depth</returns>
        public int BringForward(Woz.Entity pEntity)
        {
            // first check if this entity is in the screen
            if (!this.mEntities.Contains(pEntity))
            {
                throw new ArgumentException("Entity not found in {0}.", this.mName);
            }

            // now get the entity z index
            int originalZ = this.mEntities.IndexOf(pEntity);

            // only bring forward if its not already as forward as possible
            if (originalZ < this.mEntities.Count - 1)
            {
                // remove it from this position
                this.mEntities.RemoveAt(originalZ);

                // add it to the end of the list
                this.mEntities.Insert(originalZ + 1, pEntity);
            }

            // return the original z
            return originalZ;
        }

        /// <summary>
        /// sends the entity backward and returns its original depth
        /// </summary>
        /// <param name="pEntity">the entity to send backward</param>
        /// <exception cref="ArgumentException">if the entity is not found in the screen</exception>
        /// <returns>the original z depth</returns>
        public int SendBackward(Woz.Entity pEntity)
        {
            // first check if this entity is in the screen
            if (!this.mEntities.Contains(pEntity))
            {
                throw new ArgumentException("Entity not found in {0}.", this.mName);
            }

            // now get the entity z index
            int originalZ = this.mEntities.IndexOf(pEntity);

            // only send backward if its not already as backward as possible
            if (originalZ > 0)
            {
                // remove it from this position
                this.mEntities.RemoveAt(originalZ);

                // add it to the end of the list
                this.mEntities.Insert(originalZ - 1, pEntity);
            }

            // return the original z
            return originalZ;
        }

        /// <summary>
        /// clear the screen
        /// </summary>
        public void Clear()
        {
            // first decrement the refrence count of all entities
            foreach (Woz.Entity entity in this.mEntities)
            {
                entity.RefCount--;
            }

            // clear the list of entities
            this.mEntities.Clear();

            // update the screen
            this.UpdateThumbnail(UpdateMode.Clear, null);
        }

        /// <summary>
        /// updates the thumbnail image
        /// </summary>
        /// <param name="pMode">the mode of the update</param>
        /// <param name="pEntity">the entity to use for adding or removing modes</param>
        internal void UpdateThumbnail(UpdateMode pMode, Woz.Entity pEntity)
        {
            // create a graphics object for the thumbnail image
            Graphics thumbGraphics = Woz.InkSpaceConverter.Instance.InitializeInkGraphics(Graphics.FromImage(this.mThumbnailImage));
            thumbGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

            // setup the thumbnail image
            thumbGraphics.PageScale *= Woz.StaticVariables.Instance.ThumbnailImageRatio;

            // deal with the update according to the mode
            switch (pMode)
            {
                case UpdateMode.Clear:
                    {
                        // clear the screen to the initial background color
                        thumbGraphics.Clear(Woz.StaticVariables.Instance.InitialBackgroundColor);
                    }
                    break;

                case UpdateMode.Add:
                    {
                        // add the entity to the thumbnail
                        pEntity.Draw(thumbGraphics);
                    }
                    break;

                case UpdateMode.Remove:
                default:
                    {
                        // clear the graphics objects
                        thumbGraphics.Clear(Woz.StaticVariables.Instance.InitialBackgroundColor);

                        // now redraw all the entities
                        foreach (Woz.Entity entity in this.mEntities)
                        {
                            // tell this entity to draw itself on the thumbnail
                            entity.Draw(thumbGraphics);
                        }
                    }
                    break;
            }

            // dispose graphics object
            thumbGraphics.Dispose();

            // refresh all the thumbnails
            this.mSliderThumbnail.RefreshThumbnail();
            this.mPropagateThumbnail.RefreshThumbnail();
            this.mTransitionsThumbnail.RefreshThumbnail();
        }

        /// <summary>
        /// screen update modes
        /// </summary>
        internal enum UpdateMode
        {
            /// <summary>
            /// clears the screen so entity should be null
            /// </summary>
            Clear,

            /// <summary>
            /// adds the provided entity to the screen
            /// </summary>
            Add,

            /// <summary>
            /// removes the provided entity from the screen
            /// </summary>
            Remove
        }

        /// <summary>
        /// the next screens in the sequence
        /// </summary>
        private List<Woz.Screen> mNextScreens;

        /// <summary>
        /// the entities that are on this screen
        /// </summary>
        private List<Woz.Entity> mEntities;

        /// <summary>
        /// the selected entities
        /// </summary>
        private List<Woz.Entity> mSelectedEntities;

        /// <summary>
        /// the bounding box of the selected entities
        /// </summary>
        private Rectangle mSelectedEntitiesBoundingBox;

        /// <summary>
        /// save the screen thumbnail image
        /// </summary>
        private Bitmap mThumbnailImage;

        /// <summary>
        /// the screen thumbnail object for the screen slider associated with this screen
        /// </summary>
        private Woz.ScreenThumbnail mSliderThumbnail;

        /// <summary>
        /// the screen thumbnail object for the edit screen transitions associated with this screen
        /// </summary>
        private Woz.ScreenThumbnail mTransitionsThumbnail;

        /// <summary>
        /// the screen thumbnail object for the propagate last change window
        /// </summary>
        private Woz.ScreenThumbnail mPropagateThumbnail;

        /// <summary>
        /// whether this screen is hidden or not
        /// </summary>
        private bool mHidden;

        /// <summary>
        /// the name of this screen
        /// </summary>
        private string mName;

        /// <summary>
        /// the unique of the screen
        /// </summary>
        private uint mID;

        /// <summary>
        /// the auto generated id
        /// </summary>
        private static uint sAutoID;
    }
}
